#!/usr/bin/env bash
#
# Copyright (C) 2022 The Falco Authors.
#
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# Simple script that desperately tries to load the kernel instrumentation by
# looking for it in a bunch of ways. Convenient when running Falco inside
# a container or in other weird environments.
#

#
# Returns 1 if $cos_ver > $base_ver, 0 otherwise
#
cos_version_greater() {
	if [[ $cos_ver == "${base_ver}" ]]; then
		return 0
	fi

	#
	# COS build numbers are in the format x.y.z
	#
	a=$(echo "${cos_ver}" | cut -d. -f1)
	b=$(echo "${cos_ver}" | cut -d. -f2)
	c=$(echo "${cos_ver}" | cut -d. -f3)

	d=$(echo "${base_ver}" | cut -d. -f1)
	e=$(echo "${base_ver}" | cut -d. -f2)
	f=$(echo "${base_ver}" | cut -d. -f3)

	# Test the first component
	if [[ $a -gt $d ]]; then
		return 1
	elif [[ $d -gt $a ]]; then
		return 0
	fi

	# Test the second component
	if [[ $b -gt $e ]]; then
		return 1
	elif [[ $e -gt $b ]]; then
		return 0
	fi

	# Test the third component
	if [[ $c -gt $f ]]; then
		return 1
	elif [[ $f -gt $c ]]; then
		return 0
	fi

	# If we get here, probably malformatted version string?

	return 0
}

get_kernel_config() {
	if [ -f /proc/config.gz ]; then
		echo "* Found kernel config at /proc/config.gz"
		KERNEL_CONFIG_PATH=/proc/config.gz
	elif [ -f "/boot/config-${KERNEL_RELEASE}" ]; then
		echo "* Found kernel config at /boot/config-${KERNEL_RELEASE}"
		KERNEL_CONFIG_PATH=/boot/config-${KERNEL_RELEASE}
	elif [ -n "${HOST_ROOT}" ] && [ -f "${HOST_ROOT}/boot/config-${KERNEL_RELEASE}" ]; then
		echo "* Found kernel config at ${HOST_ROOT}/boot/config-${KERNEL_RELEASE}"
		KERNEL_CONFIG_PATH="${HOST_ROOT}/boot/config-${KERNEL_RELEASE}"
	elif [ -f "/usr/lib/ostree-boot/config-${KERNEL_RELEASE}" ]; then
		echo "* Found kernel config at /usr/lib/ostree-boot/config-${KERNEL_RELEASE}"
		KERNEL_CONFIG_PATH="/usr/lib/ostree-boot/config-${KERNEL_RELEASE}"
	elif [ -n "${HOST_ROOT}" ] && [ -f "${HOST_ROOT}/usr/lib/ostree-boot/config-${KERNEL_RELEASE}" ]; then
		echo "* Found kernel config at ${HOST_ROOT}/usr/lib/ostree-boot/config-${KERNEL_RELEASE}"
		KERNEL_CONFIG_PATH="${HOST_ROOT}/usr/lib/ostree-boot/config-${KERNEL_RELEASE}"
	elif [ -f "/lib/modules/${KERNEL_RELEASE}/config" ]; then
		# This code works both for native host and containers assuming that
		# Dockerfile sets up the desired symlink /lib/modules -> $HOST_ROOT/lib/modules
		echo "* Found kernel config at /lib/modules/${KERNEL_RELEASE}/config"
		KERNEL_CONFIG_PATH="/lib/modules/${KERNEL_RELEASE}/config"
	fi

	if [ -z "${KERNEL_CONFIG_PATH}" ]; then
		>&2 echo "Cannot find kernel config"
		exit 1
	fi

	if [[ "${KERNEL_CONFIG_PATH}" == *.gz ]]; then
		HASH=$(zcat "${KERNEL_CONFIG_PATH}" | md5sum - | cut -d' ' -f1)
	else
		HASH=$(md5sum "${KERNEL_CONFIG_PATH}" | cut -d' ' -f1)
	fi
}

get_target_id() {
	if [ -f "${HOST_ROOT}/etc/os-release" ]; then
		# freedesktop.org and systemd
		# shellcheck source=/dev/null
		source "${HOST_ROOT}/etc/os-release"
		OS_ID=$ID
	elif [ -f "${HOST_ROOT}/etc/debian_version" ]; then
		# Older debian distros
		# fixme > Can this happen on older Ubuntu?
		OS_ID=debian
	elif [ -f "${HOST_ROOT}/etc/centos-release" ]; then
		# Older CentOS distros
		OS_ID=centos
	elif [ -f "${HOST_ROOT}/etc/redhat-release" ]; then
		# Older RHEL distros
		OS_ID=rhel
	else
		return 1
	fi

	# Overwrite the OS_ID if /etc/VERSION file is present.
	# Not sure if there is a better way to detect minikube.
	if [ -f "${HOST_ROOT}/etc/VERSION" ]; then
		OS_ID=minikube
	fi

	case "${OS_ID}" in
	("amzn")
		case "${VERSION_ID}" in
		("2")
			TARGET_ID="amazonlinux2"
			;;
		("2022")
			TARGET_ID="amazonlinux2022"
			;;
		("2023")
			TARGET_ID="amazonlinux2023"
			;;
		(*)
			TARGET_ID="amazonlinux"
			;;
		esac
		;;
	("debian")
		# Workaround: debian kernelreleases might now be actual kernel running;
		# instead, they might be the Debian kernel package
		# providing the compatible kernel ABI
		# See https://lists.debian.org/debian-user/2017/03/msg00485.html
		# Real kernel release is embedded inside the kernel version.
		# Moreover, kernel arch, when present, is attached to the former,
		# therefore make sure to properly take it and attach it to the latter.
		# Moreover, we support 3 flavors for debian kernels: cloud, rt and normal.
		# KERNEL-RELEASE will have a `-rt`, or `-cloud` if we are in one of these flavors.
		# Manage it to download the correct driver.
		#
		# Example: KERNEL_RELEASE="5.10.0-0.deb10.22-rt-amd64" and `uname -v`="5.10.178-3"
		# should lead to: KERNEL_RELEASE="5.10.178-3-rt-amd64"
		TARGET_ID=$(echo "${OS_ID}" | tr '[:upper:]' '[:lower:]')
		local ARCH_extra=""
		if [[ $KERNEL_RELEASE =~ -?(rt-|cloud-|)(amd64|arm64) ]];
		then
			ARCH_extra="-${BASH_REMATCH[1]}${BASH_REMATCH[2]}"
		fi
		if [[ ${DRIVER_KERNEL_VERSION} =~ ([0-9]+\.[0-9]+\.[0-9]+\-[0-9]+) ]];
		then
			KERNEL_RELEASE="${BASH_REMATCH[1]}${ARCH_extra}"
		fi
		;;
	("ubuntu")
		# Extract the flavor from the kernelrelease
		# Examples:
		# 5.0.0-1028-aws-5.0 -> ubuntu-aws
		# 5.15.0-1009-aws -> ubuntu-aws
		if [[ $KERNEL_RELEASE =~ -([a-zA-Z]+)(-.*)?$ ]];
		then
			TARGET_ID="ubuntu-${BASH_REMATCH[1]}"
		else
			TARGET_ID="ubuntu-generic"
		fi


		# In the case that the kernelversion isn't just a number
		# we keep also the remaining part excluding `-Ubuntu`.
		# E.g.:
		# from the following `uname -v` result
		# `#26~22.04.1-Ubuntu SMP Mon Apr 24 01:58:15 UTC 2023`
		# we obtain the kernelversion`26~22.04.1`
		if [[ ${DRIVER_KERNEL_VERSION} =~ (^\#[0-9]+\~[^-]*-Ubuntu .*$) ]];
		then
			KERNEL_VERSION=$(echo "${DRIVER_KERNEL_VERSION}" | sed 's/#\([^-\\ ]*\).*/\1/g')
		fi
		;;
	("flatcar")
		KERNEL_RELEASE="${VERSION_ID}"
		TARGET_ID=$(echo "${OS_ID}" | tr '[:upper:]' '[:lower:]')
		;;
	("minikube")
		TARGET_ID=$(echo "${OS_ID}" | tr '[:upper:]' '[:lower:]')
		# Extract the minikube version. Ex. With minikube version equal to "v1.26.0-1655407986-14197" the extracted version
		# will be "1.26.0"
		if [[ $(cat ${HOST_ROOT}/etc/VERSION) =~ ([0-9]+(\.[0-9]+){2}) ]]; then
			# kernel version for minikube is always in "1_minikubeversion" format. Ex "1_1.26.0".
			KERNEL_VERSION="1_${BASH_REMATCH[1]}"
		else
			echo "* Unable to extract minikube version from ${HOST_ROOT}/etc/VERSION"
			exit 1
		fi
		;;
	("bottlerocket")
		TARGET_ID=$(echo "${OS_ID}" | tr '[:upper:]' '[:lower:]')
		# variant_id has been sourced from os-release. Get only the first variant part
		if [[ -n ${VARIANT_ID} ]];  then
			# take just first part (eg: VARIANT_ID=aws-k8s-1.15 -> aws)
			VARIANT_ID_CUT=${VARIANT_ID%%-*}
		fi
		# version_id has been sourced from os-release. Build a kernel version like: 1_1.11.0-aws
		KERNEL_VERSION="1_${VERSION_ID}-${VARIANT_ID_CUT}"
		;;
	("talos")
		TARGET_ID=$(echo "${OS_ID}" | tr '[:upper:]' '[:lower:]')
		# version_id has been sourced from os-release. Build a kernel version like: 1_1.4.1
		KERNEL_VERSION="1_${VERSION_ID}"
		;;
	(*)
		TARGET_ID=$(echo "${OS_ID}" | tr '[:upper:]' '[:lower:]')
		;;
	esac
	return 0
}

flatcar_relocate_tools() {
	local -a tools=(
		scripts/basic/fixdep
		scripts/mod/modpost
		tools/objtool/objtool
	)
	local -r hostld=$(ls /host/usr/lib64/ld-linux-*.so.*)
	local -r kdir=/lib/modules/$(ls /lib/modules/)/build
	echo "** Found host dl interpreter: ${hostld}"
	for host_tool in ${tools[@]}; do
		t=${host_tool}
		tool=$(basename $t)
		tool_dir=$(dirname $t)
		host_tool=${kdir}/${host_tool}
		if [ ! -f ${host_tool} ]; then
			continue
		fi
		umount ${host_tool} 2>/dev/null || true
		mkdir -p /tmp/${tool_dir}/
		cp -a ${host_tool} /tmp/${tool_dir}/
		echo "** Setting host dl interpreter for $host_tool"
		patchelf --set-interpreter ${hostld} --set-rpath /host/usr/lib64 /tmp/${tool_dir}/${tool}
		mount -o bind /tmp/${tool_dir}/${tool} ${host_tool}
	done
}

load_kernel_module_compile() {
	# Skip dkms on UEK hosts because it will always fail
	if [[ ${DRIVER_KERNEL_RELEASE} == *uek* ]]; then
		>&2 echo "Skipping because the dkms install always fail (on UEK hosts)"
		return
	fi

	if ! hash dkms >/dev/null 2>&1; then
		>&2 echo "This program requires dkms"
		return
	fi

	if [ "${TARGET_ID}" == "flatcar" ]; then
		KERNEL_RELEASE=${DRIVER_KERNEL_RELEASE}
		echo "* Flatcar detected (version ${VERSION_ID}); relocating kernel tools"
		flatcar_relocate_tools
	fi

	# Try to compile using all the available gcc versions
	for CURRENT_GCC in $(ls "$(dirname "$(which gcc)")"/gcc*); do
		# Filter away gcc-{ar,nm,...}
		# Only gcc compiler has `-print-search-dirs` option.
		${CURRENT_GCC} -print-search-dirs 2>&1 | grep "install:"
		if [ "$?" -ne "0" ]; then
			continue
		fi
		echo "* Trying to dkms install ${DRIVER_NAME} module with GCC ${CURRENT_GCC}"
		echo "#!/usr/bin/env bash" > "${TMPDIR}/falco-dkms-make"
		echo "make CC=${CURRENT_GCC} \$@" >> "${TMPDIR}/falco-dkms-make"
		chmod +x "${TMPDIR}/falco-dkms-make"
		if dkms install --directive="MAKE='${TMPDIR}/falco-dkms-make'" -m "${DRIVER_NAME}" -v "${DRIVER_VERSION}" -k "${KERNEL_RELEASE}" 2>/dev/null; then
			echo "* ${DRIVER_NAME} module installed in dkms"
			KO_FILE="/var/lib/dkms/${DRIVER_NAME}/${DRIVER_VERSION}/${KERNEL_RELEASE}/${ARCH}/module/${DRIVER_NAME}"
			if [ -f "$KO_FILE.ko" ]; then
				KO_FILE="$KO_FILE.ko"
			elif [ -f "$KO_FILE.ko.gz" ]; then
				KO_FILE="$KO_FILE.ko.gz"
			elif [ -f "$KO_FILE.ko.xz" ]; then
				KO_FILE="$KO_FILE.ko.xz"
			elif [ -f "$KO_FILE.ko.zst" ]; then
				KO_FILE="$KO_FILE.ko.zst"
			else
				>&2 echo "${DRIVER_NAME} module file not found"
				return
			fi
			echo "* ${DRIVER_NAME} module found: ${KO_FILE}"
			echo "* Trying to insmod"
			chcon -t modules_object_t "$KO_FILE" > /dev/null 2>&1 || true
			if insmod "$KO_FILE" > /dev/null 2>&1; then
				echo "* Success: ${DRIVER_NAME} module found and loaded in dkms"
				exit 0
			fi
			echo "* Unable to insmod ${DRIVER_NAME} module"
		else
			DKMS_LOG="/var/lib/dkms/${DRIVER_NAME}/${DRIVER_VERSION}/build/make.log"
			if [ -f "${DKMS_LOG}" ]; then
				echo "* Running dkms build failed, dumping ${DKMS_LOG} (with GCC ${CURRENT_GCC})"
				cat "${DKMS_LOG}"
			else
				echo "* Running dkms build failed, couldn't find ${DKMS_LOG} (with GCC ${CURRENT_GCC})"
			fi
		fi
	done
}

load_kernel_module_download() {
	local FALCO_KERNEL_MODULE_FILENAME="${DRIVER_NAME}_${TARGET_ID}_${KERNEL_RELEASE}_${KERNEL_VERSION}.ko"
	local URL=$(echo "${1}/${DRIVER_VERSION}/${ARCH}/${FALCO_KERNEL_MODULE_FILENAME}" | sed s/+/%2B/g)

	echo "* Trying to download a prebuilt ${DRIVER_NAME} module from ${URL}"
	if curl -L --create-dirs ${FALCO_DRIVER_CURL_OPTIONS} -o "${HOME}/.falco/${DRIVER_VERSION}/${ARCH}/${FALCO_KERNEL_MODULE_FILENAME}" "${URL}"; then
		echo "* Download succeeded"
		chcon -t modules_object_t "${HOME}/.falco/${DRIVER_VERSION}/${ARCH}/${FALCO_KERNEL_MODULE_FILENAME}" > /dev/null 2>&1 || true
		if insmod "${HOME}/.falco/${DRIVER_VERSION}/${ARCH}/${FALCO_KERNEL_MODULE_FILENAME}"; then
			echo "* Success: ${DRIVER_NAME} module found and inserted"
			exit 0
		fi
		>&2 echo "Unable to insmod the prebuilt ${DRIVER_NAME} module"
	else
		>&2 echo "Unable to find a prebuilt ${DRIVER_NAME} module"
		return
	fi
}

print_clean_termination() {
	echo
	echo "[SUCCESS] Cleaning phase correctly terminated."
	echo 
	echo "================ Cleaning phase ================"
	echo 
}

print_filename_components() {
	echo " - driver name: ${DRIVER_NAME}"
	echo " - target identifier: ${TARGET_ID}"
	echo " - kernel release: ${KERNEL_RELEASE}"
	echo " - kernel version: ${KERNEL_VERSION}"
}

clean_kernel_module() {
	echo 
	echo "================ Cleaning phase ================"
	echo 

	if ! hash lsmod > /dev/null 2>&1; then
		>&2 echo "This program requires lsmod."
		exit 1
	fi

	if ! hash rmmod > /dev/null 2>&1; then
		>&2 echo "This program requires rmmod."
		exit 1
	fi

	KMOD_NAME=$(echo "${DRIVER_NAME}" | tr "-" "_")
	echo "* 1. Check if kernel module '${KMOD_NAME}' is still loaded:"

	if ! lsmod | cut -d' ' -f1 | grep -qx "${KMOD_NAME}"; then
		echo "- OK! There is no '${KMOD_NAME}' module loaded."
		echo
	fi

	# Wait 50s = MAX_RMMOD_WAIT * 5s
	MAX_RMMOD_WAIT=10
	# Remove kernel module if is still loaded.
	while lsmod | cut -d' ' -f1 | grep -qx "${KMOD_NAME}" && [ $MAX_RMMOD_WAIT -gt 0 ]; do
		echo "- Kernel module '${KMOD_NAME}' is still loaded."
		echo "- Trying to unload it with 'rmmod ${KMOD_NAME}'..."
		if rmmod ${KMOD_NAME}; then
			echo "- OK! Unloading '${KMOD_NAME}' module succeeded."
			echo
		else
			echo "- Nothing to do...'falco-driver-loader' will wait until you remove the kernel module to have a clean termination."
			echo "- Check that no process is using the kernel module with 'lsmod | grep ${KMOD_NAME}'."
			echo "- Sleep 5 seconds..."
			echo
			((--MAX_RMMOD_WAIT))
			sleep 5
		fi
	done

	if [ ${MAX_RMMOD_WAIT} -eq 0 ]; then
		echo "[WARNING] '${KMOD_NAME}' module is still loaded, you could have incompatibility issues."
		echo
	fi
	
	if ! hash dkms >/dev/null 2>&1; then
		echo "- Skipping dkms remove (dkms not found)."
		print_clean_termination
		return
	fi

	# Remove all versions of this module from dkms.
	echo "* 2. Check all versions of kernel module '${KMOD_NAME}' in dkms:"
	DRIVER_VERSIONS=$(dkms status -m "${KMOD_NAME}" | tr -d "," | tr -d ":" | tr "/" " " | cut -d' ' -f2)
	if [ -z "${DRIVER_VERSIONS}" ]; then
		echo "- OK! There are no '${KMOD_NAME}' module versions in dkms."
	else
		echo "- There are some versions of '${KMOD_NAME}' module in dkms."
		echo
		echo "* 3. Removing all the following versions from dkms:"
		echo "${DRIVER_VERSIONS}"
		echo
	fi

	for CURRENT_VER in ${DRIVER_VERSIONS}; do
		echo "- Removing ${CURRENT_VER}..."
		if dkms remove -m ${KMOD_NAME} -v "${CURRENT_VER}" --all; then
			echo
			echo "- OK! Removing '${CURRENT_VER}' succeeded."
			echo
		else
			echo "[WARNING] Removing '${KMOD_NAME}' version '${CURRENT_VER}' failed."
		fi
	done

	print_clean_termination
}

load_kernel_module() {
	clean_kernel_module

	echo "* Looking for a ${DRIVER_NAME} module locally (kernel ${KERNEL_RELEASE})"

	local FALCO_KERNEL_MODULE_FILENAME="${DRIVER_NAME}_${TARGET_ID}_${KERNEL_RELEASE}_${KERNEL_VERSION}.ko"
	echo "* Filename '${FALCO_KERNEL_MODULE_FILENAME}' is composed of:"
	print_filename_components

	if [ -f "${HOME}/.falco/${DRIVER_VERSION}/${ARCH}/${FALCO_KERNEL_MODULE_FILENAME}" ]; then
		echo "* Found a prebuilt ${DRIVER_NAME} module at ${HOME}/.falco/${DRIVER_VERSION}/${ARCH}/${FALCO_KERNEL_MODULE_FILENAME}, loading it"
		chcon -t modules_object_t "${HOME}/.falco/${DRIVER_VERSION}/${ARCH}/${FALCO_KERNEL_MODULE_FILENAME}" > /dev/null 2>&1 || true
		insmod "${HOME}/.falco/${DRIVER_VERSION}/${ARCH}/${FALCO_KERNEL_MODULE_FILENAME}" && echo "* Success: ${DRIVER_NAME} module found and inserted"
		exit $?
	fi

	if [ -n "$ENABLE_DOWNLOAD" ]; then
		IFS=", " read -r -a urls <<< "${DRIVERS_REPO}"
		for url in "${urls[@]}"; do
			load_kernel_module_download $url
		done
	fi

	if [ -n "$ENABLE_COMPILE" ]; then
		load_kernel_module_compile
	fi

	# Last try (might load a previous driver version)
	echo "* Trying to load a system ${DRIVER_NAME} module, if present"
	if modprobe "${DRIVER_NAME}" > /dev/null 2>&1; then
		echo "* Success: ${DRIVER_NAME} module found and loaded with modprobe"
		exit 0
	fi

	# Not able to download a prebuilt module nor to compile one on-the-fly
	>&2 echo "Consider compiling your own ${DRIVER_NAME} driver and loading it or getting in touch with the Falco community"
	exit 1
}

load_bpf_probe_compile() {
	local BPF_KERNEL_SOURCES_URL=""
	local STRIP_COMPONENTS=1

	customize_kernel_build() {
		if [ -n "${KERNEL_EXTRA_VERSION}" ]; then
			sed -i "s/LOCALVERSION=\"\"/LOCALVERSION=\"${KERNEL_EXTRA_VERSION}\"/" .config
		fi
		make olddefconfig > /dev/null
		make modules_prepare > /dev/null
	}

	if [ "${TARGET_ID}" == "flatcar" ]; then
		KERNEL_RELEASE=${DRIVER_KERNEL_RELEASE}
		echo "* Flatcar detected (version ${VERSION_ID}); relocating kernel tools"
		flatcar_relocate_tools
	fi

	if [ "${TARGET_ID}" == "cos" ]; then
		echo "* COS detected (build ${BUILD_ID}), using COS kernel headers"

		BPF_KERNEL_SOURCES_URL="https://storage.googleapis.com/cos-tools/${BUILD_ID}/kernel-headers.tgz"
		KERNEL_EXTRA_VERSION="+"
		STRIP_COMPONENTS=0

		customize_kernel_build() {
			pushd usr/src/* > /dev/null || exit

			# Note: this overrides the KERNELDIR set while untarring the tarball
			KERNELDIR=$(pwd)
			export KERNELDIR

			sed -i '/^#define randomized_struct_fields_start	struct {$/d' include/linux/compiler-clang.h
			sed -i '/^#define randomized_struct_fields_end	};$/d' include/linux/compiler-clang.h

			popd > /dev/null || exit

			# Might need to configure our own sources depending on COS version
			cos_ver=${BUILD_ID}
			base_ver=11553.0.0

			cos_version_greater
			greater_ret=$?

			if [[ greater_ret -eq 1 ]]; then
			export KBUILD_EXTRA_CPPFLAGS=-DCOS_73_WORKAROUND
			fi
		}
	fi

	if [ "${TARGET_ID}" == "minikube" ]; then
		MINIKUBE_VERSION="$(cat "${HOST_ROOT}/etc/VERSION")"
		echo "* Minikube detected (${MINIKUBE_VERSION}), using linux kernel sources for minikube kernel"
		local kernel_version
		kernel_version=${DRIVER_KERNEL_RELEASE}
		local -r kernel_version_major=$(echo "${kernel_version}" | cut -d. -f1)
		local -r kernel_version_minor=$(echo "${kernel_version}" | cut -d. -f2)
		local -r kernel_version_patch=$(echo "${kernel_version}" | cut -d. -f3)

		if [ "${kernel_version_patch}" == "0" ]; then
			kernel_version="${kernel_version_major}.${kernel_version_minor}"
		fi

		BPF_KERNEL_SOURCES_URL="http://mirrors.edge.kernel.org/pub/linux/kernel/v${kernel_version_major}.x/linux-${kernel_version}.tar.gz"
	fi

	if [ -n "${BPF_USE_LOCAL_KERNEL_SOURCES}" ]; then
		local -r kernel_version_major=$(echo "${DRIVER_KERNEL_RELEASE}" | cut -d. -f1)
		local -r kernel_version=$(echo "${DRIVER_KERNEL_RELEASE}" | cut -d- -f1)
		KERNEL_EXTRA_VERSION="-$(echo "${DRIVER_KERNEL_RELEASE}" | cut -d- -f2)"

		echo "* Using downloaded kernel sources for kernel version ${kernel_version}..."

		BPF_KERNEL_SOURCES_URL="http://mirrors.edge.kernel.org/pub/linux/kernel/v${kernel_version_major}.x/linux-${kernel_version}.tar.gz"
	fi

	if [ -n "${BPF_KERNEL_SOURCES_URL}" ]; then
		get_kernel_config

		echo "* Downloading ${BPF_KERNEL_SOURCES_URL}"

		mkdir -p /tmp/kernel
		cd /tmp/kernel || exit
		cd "$(mktemp -d -p /tmp/kernel)" || exit
		if ! curl -L -o kernel-sources.tgz --create-dirs ${FALCO_DRIVER_CURL_OPTIONS} "${BPF_KERNEL_SOURCES_URL}"; then
			>&2 echo "Unable to download the kernel sources"
			return
		fi

		echo "* Extracting kernel sources"

		mkdir kernel-sources && tar xf kernel-sources.tgz -C kernel-sources --strip-components "${STRIP_COMPONENTS}"

		cd kernel-sources || exit
		KERNELDIR=$(pwd)
		export KERNELDIR

		if [[ "${KERNEL_CONFIG_PATH}" == *.gz ]]; then
			zcat "${KERNEL_CONFIG_PATH}" > .config
		else
			cat "${KERNEL_CONFIG_PATH}" > .config
		fi

		echo "* Configuring kernel"
		customize_kernel_build
	fi

	echo "* Trying to compile the eBPF probe (${BPF_PROBE_FILENAME})"

	make -C "/usr/src/${DRIVER_NAME}-${DRIVER_VERSION}/bpf" > /dev/null

	mkdir -p "${HOME}/.falco/${DRIVER_VERSION}/${ARCH}"
	mv "/usr/src/${DRIVER_NAME}-${DRIVER_VERSION}/bpf/probe.o" "${HOME}/.falco/${DRIVER_VERSION}/${ARCH}/${BPF_PROBE_FILENAME}"

	if [ -n "${BPF_KERNEL_SOURCES_URL}" ]; then
		rm -r /tmp/kernel
	fi

}

load_bpf_probe_download() {
	local URL
	URL=$(echo "${1}/${DRIVER_VERSION}/${ARCH}/${BPF_PROBE_FILENAME}" | sed s/+/%2B/g)

	echo "* Trying to download a prebuilt eBPF probe from ${URL}"

	if ! curl -L --create-dirs ${FALCO_DRIVER_CURL_OPTIONS} -o "${HOME}/.falco/${DRIVER_VERSION}/${ARCH}/${BPF_PROBE_FILENAME}" "${URL}"; then
		>&2 echo "Unable to find a prebuilt ${DRIVER_NAME} eBPF probe"
		return 1
	fi
	return 0
}

load_bpf_probe() {

	if [ ! -d /sys/kernel/debug/tracing ]; then
		echo "* Mounting debugfs"
		mount -t debugfs nodev /sys/kernel/debug
	fi

	BPF_PROBE_FILENAME="${DRIVER_NAME}_${TARGET_ID}_${KERNEL_RELEASE}_${KERNEL_VERSION}.o"
	echo "* Filename '${BPF_PROBE_FILENAME}' is composed of:"
	print_filename_components

	if [ -n "$ENABLE_DOWNLOAD" ]; then
		if [ -f "${HOME}/.falco/${DRIVER_VERSION}/${ARCH}/${BPF_PROBE_FILENAME}" ]; then
			echo "* Skipping download, eBPF probe is already present in ${HOME}/.falco/${DRIVER_VERSION}/${ARCH}/${BPF_PROBE_FILENAME}"
		else
			IFS=", " read -r -a urls <<< "${DRIVERS_REPO}"
			for url in "${urls[@]}"; do
				load_bpf_probe_download $url
				if [ $? -eq 0 ]; then
                	break
                fi
			done
		fi
	fi

	if [ -n "$ENABLE_COMPILE" ]; then
		if [ -f "${HOME}/.falco/${DRIVER_VERSION}/${ARCH}/${BPF_PROBE_FILENAME}" ]; then
			echo "* Skipping compilation, eBPF probe is already present in ${HOME}/.falco/${DRIVER_VERSION}/${ARCH}/${BPF_PROBE_FILENAME}"
		else
			load_bpf_probe_compile
		fi
	fi

	if [ -f "${HOME}/.falco/${DRIVER_VERSION}/${ARCH}/${BPF_PROBE_FILENAME}" ]; then
		echo "* eBPF probe located in ${HOME}/.falco/${DRIVER_VERSION}/${ARCH}/${BPF_PROBE_FILENAME}"

		ln -sf "${HOME}/.falco/${DRIVER_VERSION}/${ARCH}/${BPF_PROBE_FILENAME}" "${HOME}/.falco/${DRIVER_NAME}-bpf.o" \
			&& echo "* Success: eBPF probe symlinked to ${HOME}/.falco/${DRIVER_NAME}-bpf.o"
		exit $?
	else
		>&2 echo "Unable to load the ${DRIVER_NAME} eBPF probe"
		exit 1
	fi
}

print_usage() {
	echo ""
	echo "Usage:"
	echo "  falco-driver-loader [driver] [options]"
	echo ""
	echo "Available drivers:"
	echo "  module        kernel module (default)"
	echo "  bpf           eBPF probe"
	echo ""
	echo "Options:"
	echo "  --help         show brief help"
	echo "  --clean        try to remove an already present driver installation"
	echo "  --compile      try to compile the driver locally (default true)"
	echo "  --download     try to download a prebuilt driver (default true)"
	echo "  --source-only  skip execution and allow sourcing in another script"
	echo ""
	echo "Environment variables:"
	echo "  DRIVERS_REPO             specify different URL(s) where to look for prebuilt Falco drivers (comma separated)"
	echo "  DRIVER_NAME              specify a different name for the driver"
	echo "  DRIVER_INSECURE_DOWNLOAD whether you want to allow insecure downloads or not"
	echo "  DRIVER_CURL_OPTIONS      specify additional options to be passed to curl command used to download Falco drivers"
	echo "  DRIVER_KERNEL_RELEASE    specify the kernel release for which to download/build the driver in the same format used by 'uname -r' (e.g. '6.1.0-10-cloud-amd64')"
	echo "  DRIVER_KERNEL_VERSION    specify the kernel version for which to download/build the driver in the same format used by 'uname -v' (e.g. '#1 SMP PREEMPT_DYNAMIC Debian 6.1.38-2 (2023-07-27)')"
	echo ""
	echo "Versions:"
	echo "  Falco version  ${FALCO_VERSION}"
	echo "  Driver version ${DRIVER_VERSION}"
	echo ""
}

ARCH=$(uname -m)

DRIVER_KERNEL_RELEASE=${DRIVER_KERNEL_RELEASE:-$(uname -r)}
KERNEL_RELEASE=${DRIVER_KERNEL_RELEASE}

if ! hash sed > /dev/null 2>&1; then
	>&2 echo "This program requires sed"
	exit 1
fi

DRIVER_KERNEL_VERSION=${DRIVER_KERNEL_VERSION:-$(uname -v)}
KERNEL_VERSION=$(echo "${DRIVER_KERNEL_VERSION}" | sed 's/#\([[:digit:]]\+\).*/\1/')

DRIVERS_REPO=${DRIVERS_REPO:-"@DRIVERS_REPO@"}

FALCO_DRIVER_CURL_OPTIONS="-fsS --connect-timeout 5 --max-time 60 --retry 3 --retry-max-time 120"

if [ -n "$DRIVER_INSECURE_DOWNLOAD" ]
then
	FALCO_DRIVER_CURL_OPTIONS+=" -k"
fi

FALCO_DRIVER_CURL_OPTIONS+=" "${DRIVER_CURL_OPTIONS}

if [[ -z "$MAX_RMMOD_WAIT" ]]; then
	MAX_RMMOD_WAIT=60
fi

DRIVER_VERSION=${DRIVER_VERSION:-"@DRIVER_VERSION@"}
DRIVER_NAME=${DRIVER_NAME:-"@DRIVER_NAME@"}
FALCO_VERSION="@FALCO_VERSION@"

TARGET_ID="placeholder" # when no target id can be fetched, we try to build the driver from source anyway, using a placeholder name

DRIVER="module"
if [ -v FALCO_BPF_PROBE ]; then
	DRIVER="bpf"
fi

TMPDIR=${TMPDIR:-"/tmp"}

ENABLE_COMPILE=
ENABLE_DOWNLOAD=

clean=
has_args=
has_opts=
source_only=
while test $# -gt 0; do
	case "$1" in
		module|bpf)
			if [ -n "$has_args" ]; then
				>&2 echo "Only one driver per invocation"
				print_usage
				exit 1
			else
				DRIVER="$1"
				has_args="true"
				shift
			fi
			;;
		-h|--help)
			print_usage
			exit 0
			;;
		--clean)
			clean="true"
			shift
			;;
		--compile)
			ENABLE_COMPILE="yes"
			has_opts="true"
			shift
			;;
		--download)
			ENABLE_DOWNLOAD="yes"
			has_opts="true"
			shift
			;;
		--source-only)
			source_only="true"
			shift
			;;
		--*)
			>&2 echo "Unknown option: $1"
			print_usage
			exit 1
			;;
		*)
			>&2 echo "Unknown driver: $1"
			print_usage
			exit 1
			;;
	esac
done

if [ -z "$has_opts" ]; then
	ENABLE_COMPILE="yes"
	ENABLE_DOWNLOAD="yes"
fi

if [ -z "$source_only" ]; then
	echo "* Running falco-driver-loader for: falco version=${FALCO_VERSION}, driver version=${DRIVER_VERSION}, arch=${ARCH}, kernel release=${KERNEL_RELEASE}, kernel version=${KERNEL_VERSION}"

	if [ "$(id -u)" != 0 ]; then
		>&2 echo "This program must be run as root (or with sudo)"
		exit 1
	fi

	get_target_id
	res=$?
	if [ $res != 0 ]; then
		if [ -n "$ENABLE_COMPILE" ]; then
			ENABLE_DOWNLOAD=
			>&2 echo "Detected an unsupported target system, please get in touch with the Falco community. Trying to compile anyway."
		else
			>&2 echo "Detected an unsupported target system, please get in touch with the Falco community."
			exit 1
		fi
	fi

	if [ -n "$clean" ]; then
		if [ -n "$has_opts" ]; then
			>&2 echo "Cannot use --clean with other options"
			exit 1
		fi

		echo "* Running falco-driver-loader with: driver=$DRIVER, clean=yes"
		case $DRIVER in
		module)
			clean_kernel_module
			;;
		bpf)
			>&2 echo "--clean not supported for driver=bpf"
			exit 1
		esac
	else
		if ! hash curl > /dev/null 2>&1; then
			>&2 echo "This program requires curl"
			exit 1
		fi

		echo "* Running falco-driver-loader with: driver=$DRIVER, compile=${ENABLE_COMPILE:-"no"}, download=${ENABLE_DOWNLOAD:-"no"}"
		case $DRIVER in
			module)
				load_kernel_module
				;;
			bpf)
				load_bpf_probe
				;;
		esac
	fi
fi
